#include "Textbox.hpp"

namespace mi
{
    Textbox::Textbox (std::string text)  :
        Widget(tTextbox, text)
    {
        active = false;
        oneLine = false;
        editable = true;
        scrollOffset = 0;
        scrollPos = 0;
        tmpScroll = 0;
        dragScroll = false;
        textHeight = 0;
        scrollbarWidth = 3;
        drawScrollbar = false;
            
        scrollbarStart = position.y + size.y - padding;
        scrollbarEnd = position.y + padding;

        lineHeight = GetTextSize("WqAgp").y;
        textLines.clear();
        lineSpacing = 5;
        startingLine = 0;
        endingLine = 0;
        scrollMult = lineHeight + lineSpacing;

        padding = 5;

        RestructureText();
    }

    // Textbox specific functions

    bool Textbox::OneLine ()
    {
        return oneLine;
    }

    bool Textbox::OneLine (bool setTo)
    {
        bool oldval = oneLine;
        oneLine = setTo;
        return oldval;
    }

    bool Textbox::Editable ()
    {
        return editable;
    }

    bool Textbox::Editable (bool setTo)
    {
        bool oldval = editable;
        editable = setTo;
        return oldval;
    }

    // Overwrite input functions

    Widget* Textbox::MouseClick(sf::Mouse::Button button, sf::MouseState state, int x, int y)
    {
        if (button == sf::Mouse::Left)
        {
            if (state == sf::Up)
            {
                dragScroll = false;
            }
            if (state == sf::Up &&
                IsMouseInBody(Vec2i(x,y)) )
            {
                active = true;
                return this;
            }
            if (state == sf::Down &&
                IsMouseInBody(Vec2i(x,y)) )
            {
                dragScroll = true;
                return this;
            }
        }
        active = false;
        return NULL;
    }

    Widget* Textbox::MouseDragged(int x, int y, int dx, int dy)
    {
        if (dragScroll && IsMouseInBody(Vec2i(x,y)))
        {
            ScrollBy(dy);
            SetTextLines();
            return this;
        }
        return NULL;
    }
    
    Widget* Textbox::MouseWheel(int dir)
    {
        Vec2i mcoords = Vec2i(
          App::I().MousePosition().x, App::I().MousePosition().y);
        if (dir != 0 && IsMouseInBody(mcoords))
        {
            int scrollSign = 1;
            if (tmpScroll != 0)
                scrollSign = tmpScroll / abs(tmpScroll);
            int dirSign =  -( dir / abs(dir));
            if ( scrollSign != dirSign )
            {
                tmpScroll = -dir*scrollMult;
            }
            else
                tmpScroll += -dir*scrollMult;
            return this;
        }
        return NULL;
    }
    
    Widget* Textbox::KeysDown(sf::Event::KeyEvent key)
    {
        if (active && editable)
        {
          if (key.code == sf::Keyboard::Back)
            {
                text = text.substr(0, text.size() - 1);
                RestructureText();
                ScrollToBottom();
            }
            else if (key.code == sf::Keyboard::Return)
            {
                text += ('\n');
                RestructureText();
                ScrollToBottom();
            }
            if (active)
            {
                // TODO : Scroll with keyboard
            }
        }
        return NULL;
    }
    
    Widget* Textbox::KeysUp(sf::Event::KeyEvent key)
    {
        // ?
        return NULL;
    }

    Widget* Textbox::TextEntered(char ch)
    {
        if (editable && active)
        {
            if ( (ch >= 32 && ch <= 126) )
            {
                text += ch;
                RestructureText();
                ScrollToBottom();
                return this;
            }
        }
        return NULL;
    }

    //  Overwrite the draw and update

    void Textbox::Draw2D ()
    {
        if (editable && active)
            DrawFace(position, position + size);
        else
            DrawBackground(position, position + size);
        
        glScissor(position.x + padding, position.y + padding, size.x - 2*padding, size.y - 2*padding);
        
        for (unsigned int i = 0; i < textLines.size(); i++)
        {
          RenderText(textLines[i]);
        }

        glScissor(0,0,App::I().Size().x, App::I().Size().y);
        
        DrawScrollBar();
    }

    bool Textbox::Update (double dTime)
    {
        // TODO: Cursor blink, perhaps -- ?
        if (tmpScroll != 0)
        {
            int scrollSign = tmpScroll/abs(tmpScroll);
            int scrollDir = 2*scrollSign;
            scrollDir += tmpScroll / 10;
            if ( ScrollBy(scrollDir) )
            {
                tmpScroll = 0;
            }
            else
            {
                if (abs(scrollDir) > abs(tmpScroll))
                {
                    tmpScroll = 0;
                }
                else
                    tmpScroll -= scrollDir;
            }
            SetTextLines();
        }
        return false;
    }
        
    // protected functions
    void Textbox::OnSizeChange(Vec2i oldSize)
    {
        RestructureText();
    }

    void Textbox::OnPositionChange(Vec2i oldPosition)
    {
        SetTextLines();
    }

    void Textbox::OnTextChanged ()
    {
        RestructureText();
    }

    void Textbox::DrawScrollBar()
    {
        if (!drawScrollbar)
            return;
        DrawQuad( Vec2i(position.x + size.x - 1 - scrollbarWidth, scrollbarStart),
                  Vec2i(position.x + size.x - 1, scrollbarEnd), 
                  NULL, Vec3d(0.8, 0.8, 0.8) );
    }

    // Private functions

    void Textbox::RestructureText()
    {
        // check if text is non-empty
        if (text.size() == 0)
        {
          textLines.clear();
          return;
        }

        // obtain all the divided lines
        GetDividedLines();
        // set the proper text
        SetTextLines();
    }

    std::deque<std::string> Textbox::GetDividedLines()
    {
        dividedLines.clear();
        int maxWidth = size.x - 2*padding - (scrollbarWidth + 2);
        // current line and word temp storage
        std::string line = "";
        std::string word = "";

        // divide the text into lines, based on the max width

        for (unsigned int i = 0; i < text.size(); i++)
        {
            char c = text.at(i);
            // if character is newline, end current line, and push it in the lines 
            if (c == '\n')
            {
                std::string tLine = line;
                tLine += (word);  
                if (GetTextSize(tLine).x >= maxWidth)
                {
                    dividedLines.push_back(line);
                    dividedLines.push_back(word);
                    line = "";
                    word = "";
                }
                else
                {
                    dividedLines.push_back(tLine);
                    line = "";
                    word = "";
                }
            }
            else if ( (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'z') )
            {   // we're adding to the current word
                word += c;
                if (GetTextSize(word).x >= maxWidth)
                {   // the word is too long! by itself it exceed the current maxwidth
                    // push back whatever the current line is
                    dividedLines.push_back(line);
                    // push back the current word - 1 character
                    dividedLines.push_back(word.substr(0, word.length() - 1));
                    // reset the line and set the word to just the new character
                    line = "";
                    word = "";
                    word += c;
                }
            }
            else
            {   // we reached a delimiting character
                word += c;
                std::string tLine = line;
                tLine += word;
                if (GetTextSize(tLine).x < maxWidth)
                {   // the word won't make the line too long
                    line += word;
                }
                else
                {   // the word will make the line too long - so push the current line to the list, and start anew
                    dividedLines.push_back(line);
                    line = word;
                }
                // in whatever case, clear the current word
                word = "";
            }
        }
        // add last line
        std::string tLine = line;
        tLine += word;
        if (GetTextSize(tLine).x < maxWidth)
        {   // the word won't make the line too long
            line += word;
            dividedLines.push_back(line);
        }
        else
        {   // the word will make the line too long - so push the current line to the list, and start anew
            dividedLines.push_back(line);
            dividedLines.push_back(word);
        }

        std::string tmp;
        for(int i = 0; i < (int)dividedLines.size(); i++)
        {
          if (i > 0)
            tmp = tmp + "\n";
          tmp = tmp + dividedLines[i];
        }

        // finally, set the textHeight
        textHeight = (lineHeight + lineSpacing) * dividedLines.size() - lineSpacing;

        // experimental
        //textHeight = GetTextSize(tmp).y;

        ScrollBy(0);    // reset scrolling
        return dividedLines;
    }

    void Textbox::SetTextLines ()
    {
      // clear the old text lines
      textLines.clear();

      // figure out at which line to start rendering from
      int combinedLineHeight = lineHeight + lineSpacing;
      startingLine = scrollPos / combinedLineHeight;  // this should give us on which line the current scrollPos falls
      // now figure out how much to offset that starting line by
      scrollOffset = (startingLine)*combinedLineHeight - scrollPos;

      // now figure out which is the ending line
      int numLinesThatFitInBox = size.y / combinedLineHeight;
      endingLine = std::min((int)dividedLines.size() - 1, startingLine + numLinesThatFitInBox);

      int startY = apph - (position.y + size.y) + scrollOffset + padding;
      int startX = position.x + padding;

      std::string tmp;
      for (int i = startingLine; i <= endingLine; i++)
      {
        sf::Text tt = sf::Text(dividedLines[i], defaultFont, defaultFontSize);
        tt.setPosition(startX, startY + (i - startingLine)*combinedLineHeight);
        textLines.push_back(tt);
      }

      int vertSize = size.y - 2*padding;
      drawScrollbar = false;
      if (textHeight > vertSize)
      {
          scrollbarStart = (int)( position.y + (size.y - padding) - ( (int) ( (double) scrollPos ) / ( (double) textHeight ) ) * vertSize );
          scrollbarEnd =  (int)( scrollbarStart - ((double) vertSize / textHeight) * vertSize );
          drawScrollbar = true;
      }
    }

    void Textbox::ScrollToBottom()
    {
        ScrollBy(textHeight);
        SetTextLines();
    }

    bool Textbox::ScrollBy(int amount)
    {
        int newPos = scrollPos + amount;
        int max =  std::max(0, textHeight - size.y + 2*padding);
        if (newPos > max)
        {
            scrollPos = max;
            return true;
        }
        else if (newPos < 0)
        {
            scrollPos = 0;
            return true;
        }
        scrollPos = newPos;
        return false;
    }
}